﻿using System.Globalization;
using Borg.AddOn;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;

namespace Borg
{
    [Serializable]
    public class Runtime : MarshalByRefObject
    {
        private readonly string _user;

        [NonSerialized]
        private readonly Uri _environmentUri;

        private readonly ApplicationInfo _application;

        [NonSerialized]
        private readonly ReadOnlyCollection<string> _loaderDirectories;

        private readonly Dictionary<string, string> _arguments;

        [NonSerialized]
        private readonly Logger _logger;

        private VerbosityLevel _verbosityLevel;

        [NonSerialized]
        private readonly Loader _loader;

        [NonSerialized]
        private readonly IDispatcher _dispatcher;

        [NonSerialized]
        private readonly Qualifier _qualifier;

        private readonly Composer _composer;

        [NonSerialized]
        private readonly CompositionEngine _engine;

        [NonSerialized]
        private readonly GarbageCollector _gc;

        [NonSerialized]
        private readonly Activator _activator;

        private readonly Notifier _notifier;
        private readonly ExtensionMethodInvoker _invoker;

        private readonly TypeStore _typeStore;
        private readonly TypeStore.Modifier _typeStoreModifier;

        private readonly InstanceStore _instanceStore;
        private readonly InstanceStore.Modifier _instanceStoreModifier;

        [NonSerialized]
        private readonly SettingStore _settingStore;

        [NonSerialized]
        private readonly SettingProvider _settingProvider;

        [NonSerialized]
        private readonly Persistor _persistor;

        [NonSerialized]
        private readonly IList<Tuple<IAddOn, Dictionary<string, string>>> _addOns;

        [NonSerialized]
        private ReadOnlyCollection<Assembly> _addonAssemblies;

        [NonSerialized]
        private readonly AppDomain _appDomain;

        private readonly DateTime _creationTime;

        [NonSerialized]
        private RuntimeState _state;

        private DateTime _startupTime;
        private ExtensionType _rootExtensionType;
        private Extension _rootExtension;

        [NonSerialized]
        private EventHandler[] _shutdownStartedHandlers;

        [NonSerialized]
        private EventHandler[] _shutdownFinishedHandlers;

        [NonSerialized]
        private ExceptionEventHandler[] _unhandledExceptionHandlers;

        public static VersionInfo VersionInfo
        {
            get { return new VersionInfo(Assembly.GetExecutingAssembly()); }
        }

        public ApplicationInfo Application
        {
            get { return _application; }
        }

        public Uri EnvironmentUri
        {
            get { return _environmentUri; }
        }

        public string User
        {
            get { return _user; }
        }

        public ReadOnlyCollection<string> LoaderDirectories
        {
            get { return _loaderDirectories; }
        }

        public Dictionary<string, string> Arguments
        {
            get { return _arguments; }
        }

        public VerbosityLevel VerbosityLevel
        {
            get { return _verbosityLevel; }
            set { _verbosityLevel = value; }
        }

        public Loader Loader
        {
            get { return _loader; }
        }

        public IDispatcher Dispatcher
        {
            get { return _dispatcher; }
        }

        public Logger Logger
        {
            get { return _logger; }
        }

        public Qualifier Qualifier
        {
            get { return _qualifier; }
        }

        public Composer Composer
        {
            get { return _composer; }
        }

        public CompositionEngine Engine
        {
            get { return _engine; }
        }

        public GarbageCollector GC
        {
            get { return _gc; }
        }

        public TypeStore TypeStore
        {
            get { return _typeStore; }
        }

        public InstanceStore InstanceStore
        {
            get { return _instanceStore; }
        }

        public Extension RootExtension
        {
            get { return _rootExtension; }
        }

        public AppDomain AppDomain
        {
            get { return _appDomain; }
        }

        public RuntimeState State
        {
            get { return _state; }
        }

        public DateTime CreationTime
        {
            get { return _creationTime; }
        }

        public DateTime StartupTime
        {
            get { return _startupTime; }
        }

        public bool IsRunning
        {
            get { return _rootExtension != null; }
        }

        public ReadOnlyCollection<Assembly> AddonAssemblies
        {
            get { return _addonAssemblies; }
        }

        internal Activator Activator
        {
            get { return _activator; }
        }

        internal Notifier Notifier
        {
            get { return _notifier; }
        }

        internal ExtensionMethodInvoker Invoker
        {
            get { return _invoker; }
        }

        internal TypeStore.Modifier TypeStoreModifier
        {
            get { return _typeStoreModifier; }
        }

        internal TypeBuilder TypeBuilder
        {
            get { return _typeStoreModifier.TypeBuilder; }
        }

        internal InstanceStore.Modifier InstanceStoreModifier
        {
            get { return _instanceStoreModifier; }
        }

        internal Persistor Persistor
        {
            get { return _persistor; }
        }

        internal SettingStore SettingStore
        {
            get { return _settingStore; }
        }

        internal SettingProvider SettingProvider
        {
            get { return _settingProvider; }
        }

        internal ExtensionType RootExtensionType
        {
            get { return _rootExtensionType; }
        }

        public event EventHandler ShutdownStarted
        {
            add { _shutdownStartedHandlers = ArrayEx.Add(_shutdownStartedHandlers, value); }
            remove { _shutdownStartedHandlers = ArrayEx.Remove(_shutdownStartedHandlers, value); }
        }

        public event EventHandler ShutdownFinished
        {
            add { _shutdownFinishedHandlers = ArrayEx.Add(_shutdownFinishedHandlers, value); }
            remove { _shutdownFinishedHandlers = ArrayEx.Remove(_shutdownFinishedHandlers, value); }
        }

        public event ExceptionEventHandler UnhandledException
        {
            add { _unhandledExceptionHandlers = ArrayEx.Add(_unhandledExceptionHandlers, value); }
            remove { _unhandledExceptionHandlers = ArrayEx.Remove(_unhandledExceptionHandlers, value); }
        }

        protected internal Runtime(RuntimeConfig config)
        {
            _state = RuntimeState.Created;
            _creationTime = DateTime.Now;
            _user = config.User;
            _environmentUri = config.EnvironmentUri;
            _application = GetApplicationInfo(config);
            _loaderDirectories = new ReadOnlyCollection<string>(config.LoaderDirectories);
            _arguments = GetArguments(config);
            _loader = Activator.Create<Loader>(config.Loader.Type, new object[] {this});
            _verbosityLevel = config.Verbosity;
            _logger = Activator.Create<Logger>(null, new object[] {this, config.Loggers});
            _dispatcher = Activator.Create<IDispatcher>(config.Dispatcher.Type, typeof (Dispatcher), new object[0]);
            _qualifier = Activator.Create<Qualifier>(config.Qualifier.Type, new object[] {this});
            _composer = Activator.Create<Composer>(config.Composer.Type, new object[] {this});
            _engine = new CompositionEngine(this);
            _gc = new GarbageCollector(this);
            _activator = Activator.Create<Activator>(config.Activator.Type, new object[] {this});
            _notifier = Activator.Create<Notifier>(config.Notifier.Type, new object[] {this});
            _invoker = Activator.Create<ExtensionMethodInvoker>(config.Invoker.Type, new object[] {this});
            _typeStore = Activator.Create<TypeStore>(config.TypeStore.Type, new object[] {this});
            _typeStoreModifier = Activator.Create<TypeStore.Modifier>(null, new object[] {TypeStore});
            _instanceStore = Activator.Create<InstanceStore>(config.InstanceStore.Type, new object[] {this});
            _instanceStoreModifier = Activator.Create<InstanceStore.Modifier>(null, new object[] {InstanceStore});
            _settingProvider = Activator.Create<SettingProvider>(config.SettingProvider.Type,
                typeof (XmlSettingProvider), (object) this, (object) PathHelper.GetFullPath(config.SettingProvider.Path));
            _settingStore = Activator.Create<SettingStore>(config.SettingStore.Type, new object[] {this, SettingProvider});
            _persistor = Activator.Create<Persistor>(config.Persistor.Type, typeof(FilePersistor), (object)this,
                (object)PathHelper.GetFullPath(config.Persistor.Path));
            _addOns = new List<Tuple<IAddOn, Dictionary<string, string>>>();

            foreach (var addOnTypeConfig in config.AddOns)
                _addOns.Add(new Tuple<IAddOn, Dictionary<string, string>>(
                                Activator.Create<IAddOn>(addOnTypeConfig.Type, null, new object[0]),
                                addOnTypeConfig.Parameters));

            _appDomain = AppDomain.CurrentDomain;
            _appDomain.UnhandledException += OnUnhandledException;
            _dispatcher.UnhandledException += OnDispatcherUnhandledException;
            
            if (_dispatcher.CheckAccess())
                Init();
            else
                _dispatcher.Invoke(new Action(Init), new object[0]);
        }

        protected internal virtual void Start(Func<Tuple<IEnumerable<Contract>, IEnumerable<Plugin>>> getStartup)
        {
            Debug.Assert(_state == RuntimeState.Created);
            Debug.Assert(_dispatcher.CheckAccess());

            _startupTime = DateTime.Now;
            LogStartup();

            Contract contract;
            Plugin plugin;

            AssemblyAnalyzer.Analyze(Assembly.GetExecutingAssembly(),
                TypeBuilder, EnvironmentUri, out contract, out plugin);

            _typeStoreModifier.Add(new[] {contract}, new[] {plugin});

            IEnumerable<Contract> contracts;
            IEnumerable<Plugin> plugins;

            GetStartup(getStartup, out contracts, out plugins);
            LogArgs log;

            foreach (var contrct in contracts)
                if (!TypeStoreModifier.InternalCanAdd(contrct, out log))
                    Logger.Log(log);
                else
                    TypeStoreModifier.InternalAdd(contrct);

            foreach (var plugn in plugins)
                if (!TypeStoreModifier.InternalCanAdd(plugn, out log))
                    Logger.Log(log);
                else
                    TypeStoreModifier.InternalAdd(plugn);

            _state = RuntimeState.Running;
            _rootExtensionType = _typeStore.Plugins["Borg"].ExtensionTypes["Startup"];
            _rootExtension = Composer.GetShared(_rootExtensionType);

            Invoker.Invoke(_rootExtension,
                           new Action<ITypeStoreModifier>(((Startup) _rootExtension.Object).Start),
                           new object[] {TypeStoreModifier});
        }

        private static void GetStartup(Func<Tuple<IEnumerable<Contract>, IEnumerable<Plugin>>> getStartup,
            out IEnumerable<Contract> contract, out IEnumerable<Plugin> plugin)
        {
            var tuple = getStartup();

            contract = tuple.Item1;
            plugin = tuple.Item2;
        }

        internal void OnUnhandledException(object sender, ExceptionEventArgs args)
        {
            if (_unhandledExceptionHandlers != null)
                foreach (var exceptionEventHandler in _unhandledExceptionHandlers)
                    exceptionEventHandler(args);

            Logger.LogUnhandledException(args);
        }

        private void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
        {
            OnUnhandledException(sender, new ExceptionEventArgs(e.ExceptionObject));
        }

        private void OnDispatcherUnhandledException(IDispatcher sender, DispatcherExceptionEventArgs args)
        {
            OnUnhandledException(sender, new ExceptionEventArgs(args.Exception));
        }

        private void Init()
        {
            var registry = new HookRegistry(this);
            
            foreach (var addon in _addOns)
                addon.Item1.Create(registry);
            
            _addonAssemblies = registry.GetHookAssemblies().AsReadOnly();
            var compositionPermissionHook = registry.CreateCompositionPermissionHook();
            
            _logger.Init();
            _notifier.Init(compositionPermissionHook, registry.CreateNotifierNotificationHook());
            _invoker.Init(registry.CreateMethodInvokerNotificationHook());
            _typeStore.Init(registry.CreateTypeStoreNotificationHook(), registry.CreateTypeStorePermissionHook());
            _typeStoreModifier.Init(registry.CreateTypeStoreModifierApiHook());
            _instanceStore.Init(registry.CreateInstanceStoreSubstitutionHook());
            _activator.Init(registry.CreateActivatorSubstitutionHook());

            _composer.Init(registry.CreateComposerApiHook(),
                registry.CreateComposerSubstitutionHook(),
                compositionPermissionHook, registry.CreateComposerNotificationHook());

            _engine.Init(compositionPermissionHook,
                registry.CreateCompositionEngineApiHook(),
                registry.CreateCompositionEngineNotificationHook());

            _gc.Init(registry.CreateGarbageCollectorApiHook());
            _persistor.Init();
            _settingProvider.Init();
            
            foreach (var addon in _addOns)
                addon.Item1.Init(this, addon.Item2);
        }

        public void BeginShutdown()
        {
            if (_state == RuntimeState.ShutdownStarted)
                return;

            ValidateShutdown(false);
            StartShutdown();
            FinishShutdown(false);
        }

        public void Shutdown()
        {
            ValidateShutdown(true);
            StartShutdown();
            FinishShutdown(true);
        }

        private void ValidateShutdown(bool sync)
        {
            if (sync && _dispatcher.CheckAccess())
                throw new InvalidOperationException("Cannot trigger sync on shutdown from runtime thread.");
            
            if (_state > RuntimeState.Running)
                throw new InvalidOperationException("Cannot shutdown - already terminated runtime.");
        }

        private void StartShutdown()
        {
            _state = RuntimeState.ShutdownStarted;

            if (_shutdownStartedHandlers == null)
                return;

            foreach (var eventHandler in _shutdownStartedHandlers)
                eventHandler(this, EventArgs.Empty);
        }

        private void FinishShutdown(bool sync)
        {
            Action action = FinishShutdown;
            
            if (sync)
                Dispatcher.Invoke(action, new object[0]);
            else
                Dispatcher.BeginInvoke(action, new object[0]);
        }

        private void FinishShutdown()
        {
            Debug.Assert(Dispatcher.CheckAccess());
            
            GC.InternalScheduleInspectionOf(InstanceStore.Extensions.ToArray());
            GC.InternalCollect();

            Debug.Assert(!InstanceStore.Extensions.GetEnumerator().MoveNext());

            if (Persistor.IsModified)
                Persistor.Save();

            LogShutdown();

            if (_shutdownFinishedHandlers != null)
                foreach (var eventHandler in _shutdownFinishedHandlers)
                    eventHandler(this, EventArgs.Empty);

            _rootExtension = null;
            _rootExtensionType = null;

            foreach (var addon in _addOns)
                addon.Item1.Dispose();
            
            Logger.Dispose();

            if (!_dispatcher.IsDisposed)
                Dispatcher.BeginDispose();
            
            _state = RuntimeState.Terminated;
        }

        private static ApplicationInfo GetApplicationInfo(RuntimeConfig config)
        {
            return
                new ApplicationInfo(string.IsNullOrEmpty(config.Application.Name)
                                        ? "Borg"
                                        : config.Application.Name,
                                    string.IsNullOrEmpty(config.Application.Company)
                                        ? "Borg"
                                        : config.Application.Company,
                                    config.Application.Version);
        }

        private Dictionary<string, string> GetArguments(RuntimeConfig config)
        {
            var arguments = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

            if (config.Arguments != null)
                foreach (var configArg in config.Arguments)
                    arguments[configArg.Key] = configArg.Value;

            return arguments;
        }

        private void LogStartup()
        {
            var builder = new StringBuilder();

            builder.AppendLine();
            builder.AppendLine("--------------------------------------------------------");
            builder.Append("Runtime started: ");
            builder.AppendLine(StartupTime.ToString(CultureInfo.InvariantCulture));
            builder.Append("--------------------------------------------------------");
            
            Logger.Log(VerbosityLevel.Normal, LogType.Info, this, builder.ToString(), new object[0]);
        }

        private void LogShutdown()
        {
            var builder = new StringBuilder();
            var now = DateTime.Now;
            var timeSpan = now - StartupTime;

            builder.AppendLine("--------------------------------------------------------");
            builder.Append("Runtime finished: ");
            builder.AppendLine(now.ToString(CultureInfo.InvariantCulture));
            builder.AppendFormat("Uptime: {0:d2}:{1:d2}:{2:d2}", timeSpan.Hours, timeSpan.Minutes, timeSpan.Seconds);
            builder.AppendLine();
            builder.AppendLine("--------------------------------------------------------");
            
            Logger.Log(VerbosityLevel.Normal, LogType.Info, this, builder.ToString(), new object[0]);
        }
    }
}
